package org.javanum.number;

import java.math.BigDecimal;
import java.math.MathContext;

public class BigComplex extends 
			AbstractCartesianComplex<BigReal, BigComplex, BigDecimal> {

    private final BigDecimal real;
    private final BigDecimal imaginary;
    public static final BigComplex ZERO =
            new BigComplex(BigDecimal.ZERO,BigDecimal.ZERO);
    private static final BigComplex ONE =
            new BigComplex(BigDecimal.ONE,BigDecimal.ZERO);


    private BigComplex(BigDecimal real, BigDecimal imaginary) {
        this.real=real;
        this.imaginary=imaginary;
    }

    private BigComplex(BigReal real,BigReal imaginary){
        this(real.value(),imaginary.value());
    }

    /**
     * <p>Creates a new instance of {@code BigComplex}, with real
     * part {@code real} and imaginary part {@code imaginary}.</p>
     *
     * <p>Note that this parameter does not accept null arguments.
     * In the case of a Null argument being passed, an
     * {@link IllegalArgumentException} will be thrown.</p>
     *
     * <p>Note also that subclasses of BigDecimal are not considered
     * valid arguments to this method. Any argument which is a
     * subclass of BigDecimal will first be converted to a BigDecimal
     * via the {@link #BigDecimal(String)} method, where the string
     * is provided via the {@code toString()} method.</p>
     *
     * @param real the real part of the complex number
     * @param imaginary the imaginary part of the complex number
     * @return a BigComplex with real part real and imaginary part
     *          imaginary
     * @throws IllegalArgumentException if either real or imaginary
     *          is null.
     */
    public static BigComplex valueOf(BigDecimal real,
                                     BigDecimal imaginary){
        if(real==null||imaginary==null)
            throw new IllegalArgumentException("Null parameters are not" +
                    "allowed when creating BigComplex instances");
        if(real.getClass()!=BigDecimal.class){
            if(imaginary.getClass()!=BigDecimal.class){
                return new BigComplex(new BigDecimal(real.toString()),
                        new BigDecimal(imaginary.toString()));
            }
            return new BigComplex(
                    new BigDecimal(real.toString()),imaginary);
        }
        return new BigComplex(real,imaginary);
    }

    public static BigComplex valueOf(BigReal real, BigReal imaginary){
        return new BigComplex(real,imaginary);
    }

    @Override
	public BigComplex conjugate() {
        return new BigComplex(real,imaginary.negate());
	}

    @Override
    public BigComplex negate() {
        return null;  //TODO -sf- implement!
    }

    @Override
    public BigComplex multiply(BigReal value) {
        return null;  //TODO -sf- implement!
    }

    @Override
    public BigComplex add(BigReal value) {
        return null;  //TODO -sf- implement!
    }

    @Override
    public BigComplex subtract(BigReal value) {
        return null;  //TODO -sf- implement!
    }

    @Override
    public BigComplex divide(BigReal value) {
        return null;  //TODO -sf- implement!
    }

    @Override
	public BigReal imaginaryPart() {
		return new BigReal(imaginary);
	}

    /**
     * <p>Returns the Modulus of this number.</p>
     *
     * <p>WARNING: This method requires the calculation of
     * the square root of a BigDecimal to an <i>unlimited</i>
     * precision.In all but the most exceptional of situations,
     * this will result in an org.javanum.exception being thrown. It is
     * recommended that this method ONLY be used when the user is
     * confident that the square root calculation will succeed--that is,
     * in the presence of numbers which satisfy the conditions of
     * Pythagorean Triples. In all other situations,
     * {@link #modulus(java.math.MathContext)} should be called instead,
     * as this is guaranteed to return a result. </p>
     *
     * <p>For more information on the limitations of taking the
     * square root of a BigDecimal, see {@link #Numbers.bigRoot()}. </p>
     *
     * @return the modulus of this number
     * @throws ArithmeticException if one of the following conditions
     *         occur:
     *              1. the number of iterations required to compute
     *                 the square root exceeds the limit set by
     *                  {@link #Numbers.MAX_ROOT_STEPS}.
     *              2. division resulting in a Non-terminating decimal
     *                  expansion, as specified in the
     *                  {@link #BigDecimal.divide(BigDecimal)} 
     *                  documentation.
     *
     * @see {@link #Numbers.bigRoot()}
     */
	@Override
	public BigDecimal modulus() {
        return modulus(MathContext.UNLIMITED);
	}

    /**
     * <p>Returns the modulus of {@code this}, accurate to the precision
     * specified by the specified MathContext.</p>
     * 
     * @param rootContext the specified precision to compute the modulus
     *          to
     * @return the modulus of {@code this}, accurate to the precision
     *          specified by rootContext
     */
    public BigDecimal modulus(MathContext rootContext){
        BigDecimal value = real.pow(2).add(imaginary.pow(2));

        return BigMath.bigRoot(value,rootContext);
    }

	@Override
	public BigReal realPart() {
		return new BigReal(real);
	}

    //TODO -sf- document these methods!
    public BigDecimal real(){
        return real;
    }

    public BigDecimal imaginary(){
        return imaginary;
    }

    /**
     * <p>Divides {@code this} by the BigComplex {@code divisor}.</p>
     *
     * <p>WARNING: This method is equivalent to {@link #divide(BigComplex,MathContext)} with
     * {@link #MathContext.UNLIMITED}. Thus, in many situations this method can
     * result in an ArithmeticException being thrown. It is recommended that
     * {@link #divide(BigComplex,MathContext)} be used in preference to this
     * in all situations. This method is only provided to fulfill the contract
     * specified by the {@link Field} interface.</p>
     *
     * @param divisor the element to divide by
     * @return the result of dividing {@code this} by the BigComplex {@code divisor}
     * @throws ArithmeticException if one of the following conditions occur:
     *              1. {@code BigComplex.ZERO.equals(divisor)==true} as specified by
     *                  {@link #Field.divide(Field)}
     *              2. division of BigDecimal results in a non-terminating decimal expansion.
     *
     */
	@Override
	public BigComplex divide(BigComplex divisor) {
        return divide(divisor,MathContext.UNLIMITED);
	}

    /**
     * <p>Divides {@code this} by the BigComplex {@code divisor}, with a precision given by
     * the specified MathContext object.</p>
     *
     * @param divisor the BigComplex to divide by
     * @param divideContext the precision and RoundingMode with which to perform divisions.
     * @return the result of dividing {@code this} by {@code divisor}.
     * @throws ArithmeticException if {@code BigComplex.ZERO.equals(divisor)==true}, as
     *          specified by {@link Field.divide(Field)}.
     */
    public BigComplex divide(BigComplex divisor,
                             MathContext divideContext){
        if(BigComplex.ZERO.equals(divisor))
            throw new ArithmeticException("Divide by zero");
        
        BigDecimal scale = divisor.real.pow(2)
                            .add(divisor.imaginary.pow(2));

        BigDecimal realPart = real.multiply(divisor.real)
                         .add(imaginary.multiply(divisor.imaginary));

        BigDecimal imagPart = imaginary.multiply(divisor.real)
                        .subtract(real.multiply(divisor.imaginary));

        return new BigComplex(realPart.divide(scale,divideContext),
                        imagPart.divide(scale,divideContext));
    }


	@Override
	public BigComplex multiplicativeIdentity() {
		return BigComplex.ONE;
	}

	@Override
	public BigComplex multiply(BigComplex value) {
		BigDecimal realValue =
                real.multiply(value.real)
                    .subtract(
                            imaginary.multiply(value.imaginary));
        BigDecimal imaginaryValue =
                real.multiply(value.imaginary)
                        .add(imaginary.multiply(value.real));
        return new BigComplex(realValue,imaginaryValue);
	}

	@Override
	public BigComplex add(BigComplex summand) {
        return new BigComplex(real.add(summand.real),
                            imaginary.add(summand.imaginary));
	}

	@Override
	public BigComplex additiveIdentity() {
		return BigComplex.ZERO;
	}

	@Override
	public BigComplex additiveInverse() {
        return new BigComplex(real.negate(),imaginary.negate());
	}

	@Override
	public BigComplex subtract(BigComplex value) {
		return new BigComplex(real.subtract(value.real),
                imaginary.subtract(value.imaginary));
	}

	@Override
	public BigComplex multiplicativeInverse() {
		return BigComplex.ONE.divide(this);
	}

	@Override
	public BigPolarComplex polarForm() {
		// TODO Auto-generated method stub
		return null;
	}

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof BigComplex)) return false;

        BigComplex that = (BigComplex) o;

        return that.real.equals(real)
                && that.imaginary.equals(imaginary);
    }

    @Override
    public int hashCode() {
        int result = 17;
        result +=real != null ? real.hashCode() : 0;
        result = 31 * result +
                (imaginary != null ? imaginary.hashCode() : 0);
        return result;
    }
}
